package com.liao.badminton.config;


import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.liao.badminton.exception.MyAuthenticationException;
import com.liao.badminton.exception.MyAuthorException;
import com.liao.badminton.filter.JWTAuthorizationFilter;
import com.liao.badminton.filter.MyFilter;
import com.liao.badminton.handler.security.*;
import com.liao.badminton.service.PermissionApi;
import com.google.common.collect.HashMultimap;
import com.google.common.collect.Multimap;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.method.configuration.EnableGlobalMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import org.springframework.web.method.HandlerMethod;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
import org.springframework.web.servlet.mvc.method.RequestMappingInfo;
import org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerMapping;

import javax.annotation.Resource;
import javax.annotation.security.PermitAll;
import javax.servlet.MultipartConfigElement;
import java.util.*;

@EnableWebSecurity
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MyWebSecurityConfig extends WebSecurityConfigurerAdapter implements WebMvcConfigurer {

    @Resource
    MyLogoutHandler myLogoutHandler;
    @Resource
    MyAuthenticationException myAuthenticationException;
    @Resource
    MyAuthorException myAuthorException;
    @Resource
    MyUserDao myUserDao;
    @Resource
    IgnoreUrlsConfig ignoreUrlsConfig;

    @Resource
    private MyAuthenticationFailureHandler myAuthenticationFailureHandler;
    @Resource
    private MyAuthenticationSuccessHandler myAuthenticationSuccessHandler;

    @Bean
    public AuthenticationManager authenticationManager() throws Exception {
        return super.authenticationManager();
    }

    @Bean("ss") // 使用 Spring Security 的缩写，方便使用
    public SecurityFrameworkService securityFrameworkService(PermissionApi permissionApi) {
        return new SecurityFrameworkServiceImpl(permissionApi);
    }

    //配置安全拦截策略
    @Override
    protected void configure(HttpSecurity http) throws Exception {
        //链式配置拦截策略
        http.csrf().disable()//关闭csrg跨域检查
                .sessionManagement()  //session管理
//                .invalidSessionStrategy(invalidSessionStrategy) //session 失效后处理
//                .sessionAuthenticationFailureHandler(disLoginSessionHandler)//多个session处理
//                .maximumSessions(1) //只能存在一个session. 同时在一个地登录
//                .maxSessionsPreventsLogin(true) // 设置为true，即禁止后面其它人的登录
//                .expiredSessionStrategy(eruptSession)//并发处理session
                .sessionCreationPolicy(SessionCreationPolicy.STATELESS) //不要session
                .and()
                .addFilterAfter(new MyFilter(), WebAsyncManagerIntegrationFilter.class)
                .addFilterBefore(new UserNamePasswordFilter(authenticationManager(), myAuthenticationSuccessHandler, myAuthenticationFailureHandler), UsernamePasswordAuthenticationFilter.class)
                .addFilterBefore(new JWTAuthorizationFilter(authenticationManager(), ignoreUrlsConfig), FilterSecurityInterceptor.class)
        ;
        // 获得 @PermitAll 带来的 URL 列表，免登录
        Multimap<HttpMethod, String> permitAllUrls = getPermitAllUrlsFromAnnotations();
        http.userDetailsService(myUserDao)
                .authorizeRequests() //设置访问权限
                .antMatchers(ignoreUrlsConfig.getUrls())
                .permitAll()//放行静态资源
                .antMatchers(HttpMethod.GET, permitAllUrls.get(HttpMethod.GET).toArray(new String[0])).permitAll()
                .antMatchers(HttpMethod.POST, permitAllUrls.get(HttpMethod.POST).toArray(new String[0])).permitAll()
                .antMatchers(HttpMethod.PUT, permitAllUrls.get(HttpMethod.PUT).toArray(new String[0])).permitAll()
                .antMatchers(HttpMethod.DELETE, permitAllUrls.get(HttpMethod.DELETE).toArray(new String[0])).permitAll()
                .and().authorizeRequests() //设置访问权限
                .anyRequest().authenticated() //需要登录
                .and()
//                .formLogin().failureHandler(myAuthenticationFailureHandler)
//                .successHandler(myAuthenticationSuccessHandler)
//                .and()
                //.loginPage("/index.html").loginProcessingUrl("/login")
                .exceptionHandling()
//                .authenticationEntryPoint(myAuthenticationException)
                .accessDeniedHandler(myAuthorException)
                .and().logout().logoutUrl("/logout").addLogoutHandler(myLogoutHandler)//配置退出路径和退出处理
                .and()
                .cors()
                .configurationSource(configurationSource())
                .and().headers().frameOptions().disable();
    }

    /**
     * 自行注入一个PasswordEncoder。
     */
    @Bean
    public PasswordEncoder getPassWordEncoder() {
        return new BCryptPasswordEncoder(8);
    }

    public static void main(String[] args) {
        BCryptPasswordEncoder bCryptPasswordEncoder = new BCryptPasswordEncoder(8);
        String encode = bCryptPasswordEncoder.encode("123456");
        System.out.println(encode);
        System.out.println(bCryptPasswordEncoder.matches("123456", encode));
    }

    CorsConfigurationSource configurationSource() {
//         HttpWebRequestProperties.Cors cors = casProperties.getHttpWebRequest().getCors();
        CorsConfiguration corsConfiguration = new CorsConfiguration();
        corsConfiguration.setAllowedHeaders(Collections.singletonList("*"));
        corsConfiguration.setExposedHeaders(Collections.singletonList("token"));
        corsConfiguration.setAllowedMethods(Collections.singletonList("*"));
        corsConfiguration.setAllowedOrigins(Collections.singletonList("*"));
        corsConfiguration.setMaxAge(3600L);

        UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
        source.registerCorsConfiguration("/**", corsConfiguration);
        return source;
    }

    @Resource
    MultipartConfigElement multipartConfigElement;

    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        registry.addResourceHandler("/files/**").addResourceLocations("file:" + multipartConfigElement.getLocation());
    }

    @Resource
    private ApplicationContext applicationContext;

    private Multimap<HttpMethod, String> getPermitAllUrlsFromAnnotations() {
        Multimap<HttpMethod, String> result = HashMultimap.create();
        // 获得接口对应的 HandlerMethod 集合
        RequestMappingHandlerMapping requestMappingHandlerMapping = (RequestMappingHandlerMapping)
                applicationContext.getBean("requestMappingHandlerMapping");
        Map<RequestMappingInfo, HandlerMethod> handlerMethodMap = requestMappingHandlerMapping.getHandlerMethods();
        // 获得有 @PermitAll 注解的接口
        for (Map.Entry<RequestMappingInfo, HandlerMethod> entry : handlerMethodMap.entrySet()) {
            HandlerMethod handlerMethod = entry.getValue();
            if (!handlerMethod.hasMethodAnnotation(PermitAll.class)) {
                continue;
            }
            if (entry.getKey().getPatternsCondition() == null) {
                continue;
            }
            Set<String> urls = entry.getKey().getPatternsCondition().getPatterns();
            // 根据请求方法，添加到 result 结果
            entry.getKey().getMethodsCondition().getMethods().forEach(requestMethod -> {
                switch (requestMethod) {
                    case GET:
                        result.putAll(HttpMethod.GET, urls);
                        break;
                    case POST:
                        result.putAll(HttpMethod.POST, urls);
                        break;
                    case PUT:
                        result.putAll(HttpMethod.PUT, urls);
                        break;
                    case DELETE:
                        result.putAll(HttpMethod.DELETE, urls);
                        break;
                }
            });
        }
        return result;
    }

    /**
     * 更改jackson默认配置
     */
    @Bean
    public ObjectMapper objectMapper() {
        ObjectMapper objectMapper = new ObjectMapper();
        // 防止JSON名称被转义
        // objectMapper.setPropertyNamingStrategy(PropertyNamingStrategy.KEBAB_CASE);
        // 将Null值改为空字符串
        /*objectMapper.getSerializerProvider().setNullValueSerializer(new JsonSerializer<>() {
            @Override
            public void serialize(Object value, JsonGenerator jg, SerializerProvider sp) throws IOException {
                jg.writeString("");
            }
        });*/
        objectMapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
        objectMapper.disable(DeserializationFeature.ADJUST_DATES_TO_CONTEXT_TIME_ZONE);
        // 对于空的对象转json的时候不抛出错误
        objectMapper.disable(SerializationFeature.FAIL_ON_EMPTY_BEANS);
        // 禁用遇到未知属性抛出异常
        objectMapper.disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
        // null的属性不序列化（null不返回）
//        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        // 空值的属性不序列化（空字符串和null不返回）
        objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
        return objectMapper;
    }

}
